#!/usr/bin/perl
#
# AzureusStats - A simple way to display the contents of Azureus' XML
#                file via the web.
#
#      Author: Jim Turner <myztikjenz@gmail.com>
#        Date: 12.30.2005
#     Version: 1.0
#
# Requirements: This does require XML::Simple, which is ironically not very
#               easy to install.  Good luck.  
#               http://search.cpan.org/~grantm/XML-Simple-2.14/
#
# License: MIT License
#
# Copyright (c) 2005-2009 Jim Turner
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
##################################

#
# CONFIGURATION
#
# Only one step to configure, point $xmlFile to the full path location of
# your stats file.  It can be the actual file Azureus writes to as this 
# application does not write anything back to the file.
my $xmlFile = "/Library/WebServer/Documents/Azureus/Azureus_Stats.xml";

#
##################################
# Everything below here is for the inner programmer in you to diddle with.
use XML::Simple;
use POSIX;
use strict;

if( !$xmlFile )
{
    if( $ARGV[0] && (-f "$ARGV[0]") )
    {
        $xmlFile = $ARGV[0];
    }
}

my @fileStats = stat( $xmlFile );

my $xml = XMLin( $xmlFile, ForceArray => [ 'DOWNLOAD' ] );

_startHTML();

_displayGlobal( $xml, @fileStats );

foreach my $download ( @{ $xml->{DOWNLOADS}{DOWNLOAD} } )
{
    _processDownload( $download );
}

_endHTML();

#################################

sub _displayGlobal
{
    my( $xml, @fileStats ) = @_;

    my $azureusVersion       = $xml->{AZUREUS_VERSION};

    my $overallDownloadSpeed = $xml->{GLOBAL}{DOWNLOAD_SPEED}{TEXT};
    my $overallUploadSpeed   = $xml->{GLOBAL}{UPLOAD_SPEED}{TEXT};

    my $lastUpdated          = strftime( "%D %T", localtime( $fileStats[9] ));

    my( $numOfTorrents, $numOfTorrentsCompleted, $numOfPeers, $numSeeding, 
        $totalDownloaded, $totalUploaded, $totalDiscarded ) = 
        _getInterestingStatistics( $xml );

    print << "ENDglobal";
<div id = "main_container" style = "height:150px;">
    <div id = "filename">Azureus Statistics - Current as of $lastUpdated</div>
    <div id = "version_status_global">v$azureusVersion</div>
    <div id = "details_global">
        Number of Torrents: $numOfTorrents ($numOfTorrentsCompleted completed)
    </div>
    <div id = "details_global">
        Number of Peers: $numOfPeers ($numSeeding are seeding)
    </div>
    <div id = "details_global">
        Overall Download Speed: $overallDownloadSpeed
    </div>
    <div id = "details_global">
        Overall Upload Speed: $overallUploadSpeed
    </div>
    <div id = "details_global">
        Total Downloaded: $totalDownloaded
    </div>
    <div id = "details_global">
        Total Uploaded: $totalUploaded
    </div>
    <div id = "details_global">
        Total Discarded: $totalDiscarded
    </div>
</div>
<p>
<div id = "hr"><hr noshade></div>
<p>
ENDglobal

}
#################################

sub _processDownload
{
    my $d = shift;
    
    #
    # Some pre-display calculations
    #
    
    # Calculate the number of peers connected as it dictates how high 
    # we need to set the main_container
    my @peers            = _getPeers( $d );
    my $numOfPeers       = $#peers + 1;
    my $container_height = 160 + ($numOfPeers * 13);
    my $peerText         = sprintf( "(%d peer%s connected)", $numOfPeers, ($numOfPeers != 1) ? "s" : "" );

    # What status style should we use?
    my $statusStyle         = "transfer_status_incomplete";
    my $percentageRemaining = sprintf( "%.1f\% to go", (1000 - $d->{COMPLETED}) / 10 );
    if( $d->{COMPLETED} == 1000 )
    {
        $statusStyle = "transfer_status_complete";
        $percentageRemaining = "Complete!";
    }
    
    my %torrent = %{ $d->{TORRENT} };

    # Size info
    my $sizeText = $torrent{SIZE}{TEXT};
    my $sizeRaw  = $torrent{SIZE}{RAW};
    my $sizeLeftToDownload;
    if( $d->{COMPLETED} != 1000 )
    {
        $sizeLeftToDownload = _convertBytes( ($sizeRaw * sprintf( "%.1f", (1000 - $d->{COMPLETED}) / 10 )) / 100 );
        $sizeLeftToDownload .= " left";
    }
    else
    {
        $sizeLeftToDownload = "Complete";
    }

    # Fix the infinity sign and empty ETA values    
    my $eta = $d->{ETA};
    if( $eta =~ /^HASH/ )
    {
        $eta = "Finished";
    }
    if( $eta !~ /[a-zA-Z\s\d]/ )
    {
        $eta = "for-ev-er!";
    }

    # Fix empty elapsed values
    my $elapsed = $d->{ELAPSED};
    if( $elapsed =~ /^HASH/ )
    {
        $elapsed = "---";
    }
    
    # 
    # Do the output
    #
    print << "ENDhtml"; 
<div id = "main_container" style = "height:${container_height}px;">
    <div id = "filename">$torrent{NAME}</div>
    <div id = "$statusStyle">$percentageRemaining</div>
    <div id = "details_left">
        Status: $d->{DOWNLOAD_STATUS}
    </div>
    <div id = "details_right">
        Tracker Status: $d->{TRACKER_STATUS}
    </div>
    <div id = "details_left">
        Filesize: $sizeText  ($sizeLeftToDownload)
    </div>
    <div id = "details_right">&nbsp;</div>
    <div id = "details_left">
        Elapsed: $elapsed
    </div>
    <div id = "details_right">
        ETA: $eta
    </div>
    <div id = "details_left">
        Downloaded: $d->{DOWNLOADED}{TEXT}
    </div>
    <div id = "details_right">
        Download Speed: $d->{DOWNLOAD_SPEED}{TEXT}
    </div>
    <div id = "details_left">
        Uploaded: $d->{UPLOADED}{TEXT}
    </div>
    <div id = "details_right">
        Upload Speed: $d->{UPLOAD_SPEED}{TEXT}
    </div>
    <div id = "details_left">
        Discarded: $d->{DISCARDED}{TEXT}
    </div>
    <div id = "details_right">&nbsp;</div>

    <div id = "peer_title">Peer List $peerText</div>
    <div id = "peer_heading">
        <div id = "peer_element" style = "font-weight:bold;">IP Address</div>
        <div id = "peer_element" style = "font-weight:bold;">Seeding?</div>
        <div id = "peer_element" style = "font-weight:bold;">Downloaded</div>
        <div id = "peer_element" style = "font-weight:bold;">Uploaded</div>
        <div id = "peer_element" style = "font-weight:bold;">Down Speed</div>
        <div id = "peer_element" style = "font-weight:bold;">Up Speed</div>
        <div id = "peer_element" style = "font-weight:bold;">Client</div>
    </div>  
ENDhtml

    my $peerCounter = 0;
    foreach my $peer ( @peers )
    {
        my $type = $peer->{type};
        if( $type =~ m/^Unknown/ )
        {
            $type = "Unknown";
        }
        
        my $ip = $peer->{IP};
        $ip =~ s/\.\d+$/\.xx/;
        
        my $peerStyle = "peer_regular";
        if( ($peerCounter % 2) == 0 )
        {
            $peerStyle = "peer_highlight";
        }
        
        $peerCounter++;
        
        print << "ENDpeer";
    <div id = "$peerStyle">
        <div id = "peer_element">$ip</div>
        <div id = "peer_element">$peer->{IS_SEED}</div>
        <div id = "peer_element">$peer->{DOWNLOADED}{TEXT}</div>
        <div id = "peer_element">$peer->{UPLOADED}{TEXT}</div>
        <div id = "peer_element">$peer->{DOWNLOAD_SPEED}{TEXT}</div>
        <div id = "peer_element">$peer->{UPLOAD_SPEED}{TEXT}</div>
        <div id = "peer_element">$type</div>
    </div>  
ENDpeer
    }

    print << "ENDhtml";
</div>
<p>
ENDhtml
}

##########################################

sub _getInterestingStatistics
{
    my $xml = shift;

    my( $numOfTorrents, $numOfTorrentsCompleted, $numOfPeers, $numSeeding, 
        $totalDownloaded, $totalUploaded, $totalDiscarded );
    
    foreach my $download ( @{ $xml->{DOWNLOADS}{DOWNLOAD} } )
    {
        $numOfTorrents++;

        if( $download->{COMPLETED} == 1000 )
        {
            $numOfTorrentsCompleted++;
        }

        my @peers = _getPeers( $download );
        
        $numOfPeers += ($#peers + 1);
        
        foreach my $peer ( @peers )
        {
            if( $peer->{IS_SEED} eq "YES")
            {
                $numSeeding++;
            }
        }
        
        $totalDownloaded += $download->{DOWNLOADED}{RAW};
        $totalUploaded   += $download->{UPLOADED}{RAW};
        $totalDiscarded  += $download->{DISCARDED}{RAW};
    }

    return( $numOfTorrents, $numOfTorrentsCompleted, $numOfPeers, $numSeeding,
            _convertBytes( $totalDownloaded ), _convertBytes( $totalUploaded ), 
            _convertBytes( $totalDiscarded ) );
}
##########################################

sub _convertBytes
{
    my $bytes = shift;
    my( $b, $k, $M, $G, $T );

    if( $bytes <= 1024000 )
    {
        $k = sprintf( "%.1f", $bytes / 1024 );
        return "${k} KB";
    }
    elsif( ($bytes > 1024000) && ($bytes <= 1024000000) )
    {
        $M = sprintf( "%.1f", $bytes / 1024000 );
        return "${M} MB";
    }
    elsif( ($bytes > 1024000000) && ($bytes <= 1024000000000) )
    {
        $G = sprintf( "%.1f", $bytes / 1024000000 );
        return "${G} GB";
    }
    else
    {
        $T = sprintf( "%.1f", $bytes / 1024000000000 );
        return "${T} TB";
    }
}
##########################################


sub _getPeers
{
    my $d = shift;

    # <PEERS><PEER> is either an array, hash, or nothing.  If it's an
    # array, there are multiple peers (multiple hashes).  If it's a hash,
    # there is only one peer.
    # So to make this easy, we roll a single peer into an array to send
    # back.
    my @returnArray;
    if( $d->{PEERS}{PEER} =~ /^HASH/ )
    {
        push( @returnArray, $d->{PEERS}{PEER} );
    }
    elsif( $d->{PEERS}{PEER} =~ /^ARRAY/ )
    {
        @returnArray = @{ $d->{PEERS}{PEER} };
    }
    else
    {
        # No peers
        @returnArray = ();
    }

    return( @returnArray );
}
##########################################


sub _startHTML
{
    print "Content-type: text/html\n\n" if( !$ENV{SHELL} );
    
    print << "ENDhtml";
<!-- 
This file was created by AzureusStats!  Hooray!

https://sourceforge.net/projects/azureusstats

If you like what you see, go there and let me know.  Comments, suggestions?
Those are cool too.
-->
<html><head><title>Azureus Statistics</title>
<style type = "text/css">
body
{
    background-image:url( AzureusFrog.png );
    background-repeat:no-repeat;
    background-position: top right;
    background-attachment: fixed;
    color:white;
    font-family:Verdana;
    font-size:12px;
    margin:5px 0px 0px 0px;
    padding:0px;
}

#hr
{
    width:700px;
    margin:0px 50px;
    text-align:left;
}


#main_container
{
    background-color:Gainsboro;
    width:700px;
    margin:0px 50px;
    padding:0px;
    text-align:left;
    border:#000 1px solid;
}

#filename
{
    width:590px;
    background-color:DimGray;
    float:left;
    padding:5px;
    overflow: hidden;
    display:inline;
    border-bottom:#000 1px solid;
}

#version_status_global
{
    background-color:DimGray;
    width:90px;
    float:left;
    text-align:right;
    padding:5px;
    color:white;
    overflow:hidden;
    display:inline;
    border-bottom:#000 1px solid;
}

#transfer_status_complete
{
    background-color:DimGray;
    width:90px;
    float:left;
    text-align:right;
    padding:5px;
    color:limegreen;
    overflow:hidden;
    display:inline;
    border-bottom:#000 1px solid;
}

#transfer_status_incomplete
{
    background-color:DimGray;
    width:90px;
    float:left;
    text-align:right;
    padding:5px;
    color:#CC0000;
    overflow:hidden;
    display:inline;
    border-bottom:#000 1px solid;
}

#details_global
{
    margin:0px 0px 0px 100px;
    background-color:SteelBlue;
    width:598px;
    float:left;
    color:white;
    overflow:hidden;
    padding-left:2px;
}

#details_left
{
    margin:0px 0px 0px 100px;
    background-color:SteelBlue;
    width:298px;
    float:left;
    color:white;
    overflow:hidden;
    padding-left:2px;
}

#details_right
{
    background-color:SteelBlue;
    width:300px;
    float:left;
    color:white;
    overflow:hidden;
    display:inline;
}

#peer_title
{
    margin:0px 0px 0px 50px;
    background-color:DarkGray;
    width:640px;
    padding:5px;
    float:left;
    color:white;
    overflow:hidden;
}

#peer_heading
{
    margin:0px 0px 0px 50px;
    background-color:#66CDAA;
    width:650px;
    padding:0px;
    float:left;
    color:white;
    overflow:hidden;
}

#peer_highlight
{
    margin:0px 0px 0px 50px;
    background-color:LightBlue;
    width:650px;
    padding:0px;
    float:left;
    color:white;
    overflow:hidden;
}

#peer_regular
{
    margin:0px 0px 0px 50px;
    background-color:Silver;
    width:650px;
    padding:0px;
    float:left;
    color:white;
    overflow:hidden;
}

#peer_element
{
    font-size:10px;
    width: 92px;
    margin: 0px;
    float:left;
    display:inline;
    color:black;
    text-align:center;
}
</style>
</head>
<body>
ENDhtml
}

####################################

sub _endHTML
{
    print << "ENDhtml";
</body>
</html>
ENDhtml
}
